UnityAppのViewの上にUIViewとかを置く


概要

Unity advent calendar 22日目です。

http://qiita.com/advent-calendar/2012/unity

iOS用に出力したUnityのパッケージを、Xcodeで作ったプロジェクトから呼ぶ(超手前味噌)

http://sassembla.github.com/Public/2012:12:04%201-29-54/2012:12:04%201-29-54.html


の発展系で、任意のUIViewをaddし、イベントをハンドルする、っつーことをしてみました。


つまり、

プラグインを全く書かずに、Obj-CでUnity上にいろいろ置いたりできるよね、っつー話です。



今回の内容は

・UnityのViewの上に安全にUIViewを置いて、いろいろ動作するか確認 


動画

1.Unityのプロジェクトつくって、UnityConnector DLしてきて、プロジェクトにUnityApp組み込んで、シミュレータで動かす直前まで

http://youtu.be/pmQjZwvxl-o


2.それをSimulatorでビルドするまで

http://youtu.be/HDlol7y6npY



見た目

まず通常のiOSアプリが起動、この時の起動スプラッシュはUnityのものなことに注目

スクリーンショット 2012-12-23 0.06.21.png

すぐにボタンが一つセットされた画面が表示。

スクリーンショット 2012-12-23 0.06.18.png

ボタンを押すと、Unityが起動(2度目のスプラッシュ)

1__#$!@%!#__スクリーンショット 2012-12-23 0.06.21.png

一定時間後に

スクリーンショット 2012-12-23 0.06.23.png

Unityの上に、NativeコードからUIViewを追加

スクリーンショット 2012-12-23 0.06.31.png

View上のボタンを押すと、ダイアログ表示

スクリーンショット 2012-12-23 0.06.34.png

このへんのコードは、下記にupしてあります。



コード

githubにupしてあります。 Unityのアプリ部分は含んでいないので、

自分でUnity用のプロジェクトを生成して、iOSUnityApp フォルダに放り込んでね!

https://github.com/sassembla/UnityConnector


解説

アプリケーション内で、どんなことをしているのか

ステップに分けて紹介。


1.タイトルを表示


2.ボタンが押されたらUnityを起動


3.Unityが起動したらUnity上にUIを置く


4.Unity上UIのボタンを押したらアラート



1.タイトルを表示

アプリケーション起動時、ボタンのあるUIを表示

ここはまあ、ただ単に表示してるだけ。


AppDelegate.h

SampleTitleViewController * sampleVCont;


AppDelegate.m

sampleVCont = [[SampleTitleViewController alloc]initSampleTitleViewControllerWithMasterName:CONNECTOR_MASTER];

[self.window addSubview:sampleVCont.view];

ボタンが押されたときのコードは、ただ単にDelegateにそのイベントをぶん投げるもの。

NSNotificationをラップした自作のメッセージングライブラリを使っている。


さらに、AppDelegate.mでは、

    m_application = application;

    m_launchOptions = [[NSDictionary alloc]init];


みたいな感じで、UIApplicationのインスタンスと

起動時オプションの辞書(こちらは適当に空)

の2つを保持している。

UIApplicationのインスタンスも、起動時オプションも、別に必ずここでとらないといけない訳じゃない。

説明しやすい感じに、起動時に保持するようにしてみただけ。


値は、今後のUnityAppの起動やコントロールに使用する。


2.ボタンが押されたらUnityを起動

自作ライブラリで、ボタンが押された際のイベントが、下記receiverメソッドに飛んでくる。

ボタンが押されると、

SAMPLE_TITLEVIEWCONT_EXEC_UNITY_ON_TAPPED

という名前のイベントがかっ飛んでくる。


コードに①、②とかの番号振っておいたので、それ見つつ解説する。


AppDelegate.m

- (void)receiver:(NSNotification * )notif {

    NSString * exec = [messenger getExecFromNotification:notif];

    NSDictionary * dict = [messenger getTagValueDictionaryFromNotification:notif];

    

    if ([exec isEqualToString:SAMPLE_TITLEVIEWCONT_EXEC_UNITY_ON_TAPPED]) {

/*

ignite Unity

*/

        [messenger call:KS_UNITYBOOTER withExec:KS_UNITYBOOTER_EXEC_INITIALIZE,

         [messenger tag:@"application" val:m_application],

         [messenger tag:@"launchOptions" val:m_launchOptions],

         nil];

/*

generate pinView(back button inside.)

*/

SamplePinViewControler * samplePinViewCont = [[SamplePinViewControler alloc]init];

/*

set pinView to Unity-window after some seconds.(this is for experimental.)

*/

[messenger callMyself:CONNECTOR_MASTER_EXEC_SET_VIEW_TO_UNITYWINDOW,

[messenger tag:@"view" val:samplePinViewCont.view],

[messenger withDelay:5.0],

nil];

    }


①Unity起動

Unityの起動クラスをラップしているKSUnityBooterのインスタンス(こちらも自作のライブラリ使用)に、

メッセージを送る。


値としてm_applicationm_launchOptionsを送付。


通信する先は、KSUnityBooter.mm。

ライブラリによって、receiverメソッドへと、イベントが届く。


KSUnityBooter.mmは、UnityAppそれ自体のmain.mmの内容を模している。

必要なimportがあり、intがある。

AppControllerクラスのインスタンスとして、 unityAppという名前のインスタンスを保持するようにしている。


KSUnityBooter.mm

#import "AppController.h"

#import "RegisterClasses.h"

#import "RegisterMonoModules.h"



static const int constsection = 0;

bool UnityParseCommandLine(int argc, char *argv[]);


AppController * unityApp;


- (void) receiver:(NSNotification * )notif {

    NSString * exec = [messenger getExecFromNotification:notif];

    NSDictionary * dict = [messenger getTagValueDictionaryFromNotification:notif];

    

    

    //unity-app wrapper with UIApplicationDelegate

    /*

     o     - (void)applicationDidFinishLaunching:(UIApplication *)application;

(中略)

     o     - (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window  NS_AVAILABLE_IOS(6_0);

     */

    

    if ([exec isEqualToString:KS_UNITYBOOTER_EXEC_INITIALIZE]) {

        NSAssert([dict valueForKey:@"application"],@"application required");

        NSAssert([dict valueForKey:@"launchOptions"], @"launchOptions required");

        

        UIApplication * application = [dict valueForKey:@"application"];

        NSDictionary * launchOptions = [dict valueForKey:@"launchOptions"];

        

        RegisterMonoModules();

        

        unityApp = [[AppController alloc]init];

        [unityApp application:application didFinishLaunchingWithOptions:launchOptions];

    }


ここで、UnityAppの中に入ってる、AppControllerクラスのインスタンスunityAppを初期化。

UnityAppそれ自体が起動したのと同じ挙動にするために、

AppDelegateから送付してきたm_applicationm_launchOptionsをパラメータに使う。


これで、Unityの起動が開始。

アプリの画面にUnityのスプラッシュが表示されるようになる。


ちなみに、非プロ版時のみ表示されるスプラッシュだけど、用意されたファイルを使わないようにすると

きちんとエラーで落ちる。

悪いことをしてはいけない。


で、UinityApp自体は点火できたので、次はUnity上にiOSのUIを置く。


②Unity上にUIを置く

ここでは、

アラートをセットしてあるボタンが置いてあるUIViewのSamplePinViewControlerをインスタンス化。

そのviewを、5.0秒後にそのインスタンスを自分自身(AppDelegate.mのインスタンス)へと送る。


3.Unityが起動したらUnity上にUIを置く

UnityAppの起動完了 = Unityのスプラッシュが消える、なのだけれど、UnityAppに手を加えないと

それが察知できない。

(書いてて気づいたけどもしかしたらNSNotificationでAccelの開始とかをハンドルすればわかるかもしれない。)


なので、だいたいで5秒たったら、起動してるものとする。

そのタイミングで、下記コードが呼ばれる。


AppDelegate.m

- (void)receiver:(NSNotification * )notif {


(中略)

if ([exec isEqualToString:CONNECTOR_MASTER_EXEC_SET_VIEW_TO_UNITYWINDOW]) {

NSLog(@"m_application %@", [m_application windows]);

NSAssert([dict valueForKey:@"view"], @"view required");

//get the view what want to append to Unity.

UIView * testView = [dict valueForKey:@"view"];

//get window array of this app

NSArray * windowArray = [m_application windows];

// for (UIWindow * window in [m_application windows]) {

// NSLog(@"[window subviews] %@", [window subviews]);

// }

//append view onto Unity-window

if (1 < [windowArray count]) {

[[windowArray objectAtIndex:1] addSubview:testView];

}

}


UnityのViewが作られたあと、Unityが稼働しているAppには、UIWindowが2つある。

旧来の、App起動時からあるUIWindowは実際の画面から引きはがされている(でもnilとかではない、、)

ここでは、2つめのUIWindowを適当に検出して、そこに先ほどのviewをaddSubviewしている。


このUnity用のWIndowは、Unityのインスタンス生成直後から存在はしてるんだけど、

いきなりaddSubviewすると、Unityの起動完了時にremoveされる。



4.Unity上UIのボタンを押したらアラート

UnityのUIWindow上に乗っけているSamplePinViewControlerは、こんな感じ。


SamplePinViewControler.m

- (IBAction)tapped:(id)sender {

UIAlertView * alertView = [[UIAlertView alloc]initWithTitle:@"active" message:@"can react UI-something" delegate:self cancelButtonTitle:@"DONE" otherButtonTitles:nil];

[alertView show];

}


xibで単純に組んでいて、ボタンが押されたら(touchUpInside) アラートを表示するだけ。



以上!